home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 26 / Cream of the Crop 26.iso / os2 / octa209s.zip / octave-2.09 / libs / kpathsea / elt-dirs.c < prev    next >
C/C++ Source or Header  |  1995-09-29  |  11KB  |  377 lines

  1. /* elt-dirs.c: Translate a path element to its corresponding director{y,ies}.
  2.  
  3. Copyright (C) 1993, 94 Karl Berry.
  4.  
  5. This program is free software; you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation; either version 2, or (at your option)
  8. any later version.
  9.  
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. GNU General Public License for more details.
  14.  
  15. You should have received a copy of the GNU General Public License
  16. along with this program; if not, write to the Free Software
  17. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.  */
  18.  
  19. #include <kpathsea/config.h>
  20.  
  21. #include <kpathsea/c-pathch.h>
  22. #include <kpathsea/expand.h>
  23. #include <kpathsea/fn.h>
  24. #include <kpathsea/pathsearch.h>
  25. #include <kpathsea/xopendir.h>
  26.  
  27. /* To avoid giving prototypes for all the routines and then their real
  28.    definitions, we give all the subroutines first.  The entry point is
  29.    the last routine in the file.  */
  30.  
  31. /* Make a copy of DIR (unless it's null) and save it in L.  Ensure that
  32.    DIR ends with a DIR_SEP for the benefit of later searches.  */
  33.  
  34. static void
  35. dir_list_add P2C(str_llist_type *, l,  const_string, dir)
  36. {
  37.   string saved_dir
  38.     = IS_DIR_SEP (dir[strlen (dir) - 1])
  39.       ? xstrdup (dir)
  40.       : concat (dir, DIR_SEP_STRING);
  41.   
  42.   str_llist_add (l, saved_dir);
  43. }
  44.  
  45.  
  46. /* If DIR is a directory, add it to the list L.  */
  47.  
  48. static void
  49. checked_dir_list_add P2C(str_llist_type *, l,  const_string, dir)
  50. {
  51.   if (dir_p (dir))
  52.     dir_list_add (l, dir);
  53. }
  54.  
  55. /* The cache.  Typically, several paths have the same element; for
  56.    example, /usr/local/lib/texmf/fonts//.  We don't want to compute the
  57.    expansion of such a thing more than once.  Even though we also cache
  58.    the dir_links call, that's not enough -- without this path element
  59.    caching as well, the execution time doubles.  */
  60.  
  61. typedef struct
  62. {
  63.   const_string key;
  64.   str_llist_type *value;
  65. } cache_entry;
  66.  
  67. static cache_entry *the_cache = NULL;
  68. static unsigned cache_length = 0;
  69.  
  70.  
  71. /* Associate KEY with VALUE.  We implement the cache as a simple linear
  72.    list, since it's unlikely to ever be more than a dozen or so elements
  73.    long.  We don't bother to check here if PATH has already been saved;
  74.    we always add it to our list.  We copy KEY but not VALUE; not sure
  75.    that's right, but it seems to be all that's needed.  */
  76.  
  77. static void
  78. cache P2C(const_string, key,  str_llist_type *, value)
  79. {
  80.   cache_length++;
  81.   XRETALLOC (the_cache, cache_length, cache_entry);
  82.   the_cache[cache_length - 1].key = xstrdup (key);
  83.   the_cache[cache_length - 1].value = value;
  84. }
  85.  
  86.  
  87. /* To retrieve, just check the list in order.  */
  88.  
  89. static str_llist_type *
  90. cached P1C(const_string, key)
  91. {
  92.   unsigned p;
  93.   
  94.   for (p = 0; p < cache_length; p++)
  95.     {
  96.       if (STREQ (the_cache[p].key, key))
  97.         return the_cache[p].value;
  98.     }
  99.   
  100.   return NULL;
  101. }
  102.  
  103. /* Handle the magic path constructs.  */
  104.  
  105. /* Declare recursively called routine.  */
  106. static void expand_elt P3H(str_llist_type *, const_string, unsigned);
  107.  
  108.  
  109. /* POST is a pointer into the original element (which may no longer be
  110.    ELT) to just after the doubled DIR_SEP, perhaps to the null.  Append
  111.    subdirectories of ELT (up to ELT_LENGTH, which must be a /) to
  112.    STR_LIST_PTR.  */
  113.  
  114. static void
  115. do_subdir P4C(str_llist_type *, str_list_ptr,  const_string, elt,
  116.               unsigned, elt_length,  const_string, post)
  117. {
  118.   DIR *dir;
  119.   struct dirent *e;
  120.   fn_type name;
  121.   
  122.   /* Some old compilers don't allow aggregate initialization.  */
  123.   name = fn_copy0 (elt, elt_length);
  124.   
  125.   assert (IS_DIR_SEP (elt[elt_length - 1]));
  126.   
  127.   /* If we can't open it, quit.  */
  128.   dir = opendir (FN_STRING (name));
  129.   if (dir == NULL)
  130.     {
  131.       fn_free (&name);
  132.       return;
  133.     }
  134.   
  135.   /* Include top level before subdirectories, if nothing to match.  */
  136.   if (*post == 0)
  137.     dir_list_add (str_list_ptr, FN_STRING (name));
  138.   else
  139.     { /* If we do have something to match, see if it exists.  For
  140.          example, POST might be `pk/ljfour', and they might have a
  141.          directory `$TEXMF/fonts/pk/ljfour' that we should find.  */
  142.       fn_str_grow (&name, post);
  143.       if (dir_p (FN_STRING (name)))
  144.         dir_list_add (str_list_ptr, FN_STRING (name));
  145.       fn_shrink_to (&name, elt_length);
  146.     }
  147.  
  148.   while ((e = readdir (dir)) != NULL)
  149.     { /* If it begins with a `.', never mind.  (This allows ``hidden''
  150.          directories that the algorithm won't find.)  */
  151.       if (e->d_name[0] != '.')
  152.         {
  153.           int links;
  154.           
  155.           /* Construct the potential subdirectory name.  */
  156.           fn_str_grow (&name, e->d_name);
  157.           
  158.           /* If we can't stat it, or if it isn't a directory, continue.  */
  159.           links = dir_links (FN_STRING (name));
  160.  
  161.           if (links >= 0)
  162.             { 
  163.               unsigned potential_len = FN_LENGTH (name);
  164.               
  165.               /* It's a directory, so append the separator.  */
  166.               fn_str_grow (&name, DIR_SEP_STRING);
  167.               
  168.               if (*post != 0)
  169.                 { 
  170.                   fn_str_grow (&name, post);
  171.                   /* Unfortunately we can't check if the new element is
  172.                      a leaf directory, because we don't have a directory
  173.                      name here, we just have a path spec. This means we
  174.                      may descend into a leaf directory cm/pk, if the
  175.                      spec is ...fonts//pk//.  */
  176.                   expand_elt (str_list_ptr, FN_STRING (name), potential_len);
  177.                   fn_shrink_to (&name, potential_len);
  178.                 }
  179.               
  180.               /* Should we recurse?  To see if the subdirectory is a
  181.                  leaf, check if it has two links (one for . and one for
  182.                  ..).  This means that symbolic links to directories do
  183.                  not affect the leaf-ness.  This is arguably wrong, but
  184.                  the only alternative I know of is to stat every entry
  185.                  in the directory, and that is unacceptably slow.
  186.                  
  187.                  The #ifdef here makes all this configurable at
  188.                  compile-time, so that if we're using VMS directories or
  189.                  some such, we can still find subdirectories, even if it
  190.                  is much slower.  */
  191. #ifdef UNIX_ST_NLINK
  192.               if (links > 2)
  193. #endif
  194.                 /* All criteria are met; find subdirectories.  */
  195.                 do_subdir (str_list_ptr, FN_STRING (name),
  196.                            potential_len, post);
  197. #ifdef UNIX_ST_NLINK
  198.               else if (*post == 0)
  199.                 /* Nothing to match, no recursive subdirectories to
  200.                    look for: we're done with this branch.  Add it.  */
  201.                 dir_list_add (str_list_ptr, FN_STRING (name));
  202. #endif
  203.             }
  204.  
  205.           /* Remove the directory entry we just checked from `name'.  */
  206.           fn_shrink_to (&name, elt_length);
  207.         }
  208.     }
  209.   
  210.   fn_free (&name);
  211.   xclosedir (dir);
  212. }
  213.  
  214.  
  215. /* Assume ELT is non-empty and non-NULL.  Return list of corresponding
  216.    directories (with no terminating NULL entry) in STR_LIST_PTR.  Start
  217.    looking for magic constructs at START.  */
  218.  
  219. static void
  220. expand_elt P3C(str_llist_type *, str_list_ptr,  const_string, elt,
  221.                unsigned, start)
  222. {
  223.   boolean found_special = false;
  224.   const_string dir = elt + start;
  225.   
  226.   while (*dir != 0)
  227.     {
  228.       if (IS_DIR_SEP (*dir))
  229.         {
  230.           /* If two consecutive directory separators, find subdirectories.  */
  231.           if (IS_DIR_SEP (dir[1]))
  232.             {
  233.               do_subdir (str_list_ptr, elt, dir - elt + 1, dir + 2);
  234.               found_special = true;
  235.             }
  236. #if 0
  237. /* Maybe eventually I'll implement this, but probably not.  */
  238.           /* If /?, make following component optional.  */
  239.           else if (dir[1] == '?')
  240.             do_optional (str_list_ptr, elt, dir - elt + 1, dir + 2);
  241. #endif
  242.           /* No special stuff at this slash.  Keep going.  */
  243.         }
  244.       
  245.       dir++;
  246.     }
  247.   
  248.   if (!found_special)
  249.     /* When we reach the end of ELT, it will be a normal filename.  */
  250.     checked_dir_list_add (str_list_ptr, elt);
  251. }
  252.  
  253. /* Here is the entry point.  Returns directory list for ELT.  */
  254.  
  255. str_llist_type *
  256. kpse_element_dirs P1C(const_string, elt)
  257. {
  258.   str_llist_type *ret;
  259.  
  260.   /* If given nothing, return nothing.  */
  261.   if (!elt)
  262.     return NULL;
  263.  
  264.   /* If we've already cached the answer for ELT, return it.  */
  265.   ret = cached (elt);
  266.   if (ret)
  267.     return ret;
  268.  
  269.   /* We're going to have a real directory list to return.  */
  270.   ret = XTALLOC1 (str_llist_type);
  271.   *ret = NULL;
  272.   
  273.   /* If ELT is the empty string, just return cwd.  */
  274.   if (*elt == 0)
  275.     { /* Some old compilers do not support aggregate initialization.  */
  276.       char cwd[3];
  277.       cwd[0] = '.';
  278.       cwd[1] = DIR_SEP;
  279.       cwd[2] = 0;
  280.       
  281.       checked_dir_list_add (ret, cwd);
  282.     }
  283.  
  284.   /* OK, so much for the trivial cases.  We handle the hard case in
  285.      a subroutine.  */
  286.   else
  287.     {
  288.       /* If the path starts with ~ or ~user, expand it.  Do this
  289.          before calling `expand_subdir' or `add_directory', so that
  290.          we don't expand the same ~ over and over.  */
  291.       string dir = kpse_expand (elt);
  292.  
  293.       expand_elt (ret, dir, 0);
  294.  
  295.       free (dir);
  296.     }
  297.  
  298.   /* Remember the directory list we just found, in case future calls are
  299.      made with the same ELT.  */
  300.   cache (elt, ret);
  301.  
  302. #ifdef DEBUG
  303.   if (KPSE_DEBUG_P (KPSE_DEBUG_EXPAND))
  304.     {
  305.       DEBUGF1 ("path element %s =>", elt);
  306.       if (ret)
  307.         {
  308.           str_llist_elt_type *e;
  309.           for (e = *ret; e; e = STR_LLIST_NEXT (*e))
  310.             fprintf (stderr, " %s", STR_LLIST (*e));
  311.         }
  312.       putc ('\n', stderr);
  313.       fflush (stderr);
  314.     }
  315. #endif
  316.  
  317.   return ret;
  318. }
  319.  
  320. #ifdef TEST
  321.  
  322. void
  323. print_element_dirs (const_string elt)
  324. {
  325.   str_llist_type *dirs;
  326.   
  327.   printf ("Directories of %s:\t", elt ? elt : "(null)");
  328.   fflush (stdout);
  329.   
  330.   dirs = kpse_element_dirs (elt);
  331.   
  332.   if (!dirs)
  333.     printf ("(null)");
  334.   else
  335.     {
  336.       str_llist_elt_type *dir;
  337.       for (dir = *dirs; dir; dir = STR_LLIST_NEXT (*dir))
  338.         {
  339.           string d = STR_LLIST (*dir);
  340.           printf ("%s ", *d ? d : "`'");
  341.         }
  342.     }
  343.   
  344.   putchar ('\n');
  345. }
  346.  
  347. int
  348. main ()
  349. {
  350.   /* DEBUG_SET (DEBUG_STAT); */
  351.  
  352.   /* All lists end with NULL.  */
  353.   print_element_dirs (NULL);    /* */
  354.   print_element_dirs ("");    /* ./ */
  355.   print_element_dirs ("/k");    /* */
  356.   print_element_dirs (".//");    /* ./ ./archive/ */
  357.   print_element_dirs (".//archive");    /* ./ ./archive/ */
  358.   print_element_dirs ("/tmp/fonts//");    /* no need to stat anything */
  359.   print_element_dirs ("/usr/local/lib/tex/fonts//");      /* lots */
  360.   print_element_dirs ("/usr/local/lib/tex/fonts//times"); /* just one */
  361.   print_element_dirs ("/usr/local/lib/tex/fonts//"); /* lots again [cache] */
  362.   print_element_dirs ("~karl");        /* tilde expansion */
  363.   print_element_dirs ("$karl");        /* variable expansion */  
  364.   print_element_dirs ("~${LOGNAME}");    /* both */  
  365.   
  366.   return 0;
  367. }
  368.  
  369. #endif /* TEST */
  370.  
  371.  
  372. /*
  373. Local variables:
  374. test-compile-command: "gcc -g -I. -I.. -DTEST elt-dirs.c kpathsea.a"
  375. End:
  376. */
  377.